`include "common.svh"
`define ROB_DIFFTEST
`ifdef ROB_DIFFTEST
import "DPI-C" function int rob_create(
  int id,
  int size,
  string name
);
import "DPI-C" function void rob_push(
  int id,
  int index,
  int ptr
);
import "DPI-C" function void rob_pop(
  int id,
  int index,
  int ptr,
  bit issue,
  bit finish,
  bit exeception,
  int exeception_code
);
import "DPI-C" function void rob_modify(
  int id,
  int index,
  int ptr,
  bit issue,
  bit finish,
  bit exeception,
  int exeception_code
);
import "DPI-C" function void rob_redirect(
  int id,
  int ptr,
  bit issue,
  bit finish,
  bit exeception,
  int exeception_code
);

`endif
module rob #(
    parameter WRITE_WIDTH = 2,
    parameter ISSUE_WIDTH = 2,
    parameter COMMIT_WIDTH = 2,
    parameter BRU_NUM = 1,
    parameter LSU_NUM = 1
) (
    input clk,
    input rst,

    input i_write_valid,
    input i_write_op_valid[WRITE_WIDTH-1:0],
    input ROB_Entry i_write_op[WRITE_WIDTH-1:0],
    output ROB_PTR i_write_rob_idx[WRITE_WIDTH-1:0],
    output i_write_ready,

    input i_issue_valid[ISSUE_WIDTH-1:0],
    input ROB_PTR i_issue_rob_idx[ISSUE_WIDTH-1:0],

    input i_finish_valid[ISSUE_WIDTH-1:0],
    input ROB_PTR i_finish_rob_idx[ISSUE_WIDTH-1:0],

    input i_exeception_valid[ISSUE_WIDTH-1:0],
    input ROB_PTR i_exeception_rob_idx[ISSUE_WIDTH-1:0],
    input EXPT_CODE i_exeception_code[ISSUE_WIDTH-1:0],

    input i_bru_bpred_miss_valid[BRU_NUM-1:0],
    input ROB_PTR i_bru_rob_idx[BRU_NUM-1:0],
    input word_t i_bru_dnpc[BRU_NUM-1:0],
    input BPU_Res i_bru_correct_bpu_res[BRU_NUM-1:0],

    input i_misc_redirect_valid,
    input ROB_PTR i_misc_redirect_rob_idx,
    input word_t i_misc_redirect_pc,

    input i_replay_valid[ISSUE_WIDTH-1:0],
    input ROB_PTR i_replay_rob_idx[ISSUE_WIDTH-1:0],

    input i_mmio_valid[LSU_NUM-1:0],
    input ROB_PTR i_mmio_rob_idx[LSU_NUM-1:0],

    output o_order_valid,
    output ROB_PTR o_order_rob_idx,

    output o_commit_valid,
    output o_commit_op_valid[COMMIT_WIDTH-1:0],
    output ROB_Entry o_commit_op[COMMIT_WIDTH-1:0],
    output ROB_State o_commit_op_state[COMMIT_WIDTH-1:0],
    output o_commit_restore_gc[COMMIT_WIDTH-1:0],
    input o_commit_ready,

    output o_redirect_valid,
    output o_redirect_replay,
    output o_redirect_bpu_res_valid,
    output BPU_Res o_redirect_bpu_res,
    output word_t o_redirect_pc
);
  localparam BPU_RES_SIZE = $size(BPU_Res);
  localparam EXPT_CODE_SIZE = $size(EXPT_CODE);
  localparam ROB_ENTRY_SIZE = $size(ROB_Entry);

  ROB_Entry rob_entrys[`ROB_SIZE-1:0];
  ROB_State rob_states[`ROB_SIZE-1:0];

  logic op_finish[`ROB_SIZE-1:0];
  logic op_issue[`ROB_SIZE-1:0];
  logic op_exeception[`ROB_SIZE-1:0];
  EXPT_CODE op_exeception_code[`ROB_SIZE-1:0];

  //for difftest
  logic op_mmio[`ROB_SIZE-1:0];

  BPU_Res op_correct_bpu_res[`ROB_SIZE-1:0];
  word_t op_dnpc[`ROB_SIZE-1:0]  /*verilator public_flat*/;
  logic op_bpred_miss[`ROB_SIZE-1:0];

  logic op_replay[`ROB_SIZE-1:0];
  always_comb begin
    integer i;
    for (i = 0; i < `ROB_SIZE; i = i + 1) begin
      rob_states[i] = 'b0;
      rob_states[i].finish = op_finish[i];
      rob_states[i].issue = op_issue[i];
      rob_states[i].exeception = op_exeception[i];
      rob_states[i].exeception_code = op_exeception_code[i];
      rob_states[i].bpred_miss = op_bpred_miss[i];
      rob_states[i].replay = op_replay[i];
      rob_states[i].mmio = op_mmio[i];
    end
  end
  ROB_PTR write_ptr, commit_ptr;
  ROB_PTR write_ptr_next, commit_ptr_next;
  logic write_ptr_flag, commit_ptr_flag;
  logic write_ptr_flag_next, commit_ptr_flag_next;

  ROB_PTR write_num, commit_num;
  always_comb begin
    integer i;
    write_num = 'b0;
    for (i = 0; i < WRITE_WIDTH; i = i + 1) begin
      write_num = write_num + (i_write_op_valid[i] ? 'b1 : 'b0);
    end
    commit_num = 'b0;
    for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
      commit_num = commit_num + (o_commit_op_valid[i] ? 'b1 : 'b0);
    end
  end
  //Generate write ptr
  wire [`ROB_AW:0] write_sum = write_ptr + write_num;
  wire [`ROB_AW-1:0] write_ptr_next_round = write_sum[`ROB_AW-1:0] - `ROB_SIZE;
  wire write_ptr_flag_invert = (write_sum >= `ROB_SIZE);
  assign write_ptr_flag_next = write_ptr_flag ^ write_ptr_flag_invert;
  assign write_ptr_next =  write_ptr_flag_invert ? write_ptr_next_round[`ROB_AW-1:0] : write_sum[`ROB_AW-1:0];
  wire i_write_fire = i_write_valid & i_write_ready;
  reg_lr #(`ROB_AW) write_ptr_r (
      .rst (rst | o_redirect_valid),
      .load(i_write_fire),
      .din (write_ptr_next),
      .dout(write_ptr),
      .*
  );
  reg_lr #(1) write_ptr_flag_r (
      .rst (rst | o_redirect_valid),
      .load(i_write_fire),
      .din (write_ptr_flag_next),
      .dout(write_ptr_flag),
      .*
  );
  //Generate commit ptr
  wire [`ROB_AW:0] commit_sum = commit_ptr + commit_num;
  wire [`ROB_AW-1:0] commit_ptr_next_round = commit_sum[`ROB_AW-1:0] - `ROB_SIZE;
  wire commit_ptr_flag_invert = (commit_sum >= `ROB_SIZE);
  assign commit_ptr_flag_next = commit_ptr_flag ^ commit_ptr_flag_invert;
  assign commit_ptr_next = commit_ptr_flag_invert ? commit_ptr_next_round[`ROB_AW-1:0] : commit_sum[`ROB_AW-1:0];
  wire o_commit_fire = o_commit_valid & o_commit_ready;
  reg_lr #(`ROB_AW) commit_ptr_r (
      .rst (rst | o_redirect_valid),
      .load(o_commit_fire),
      .din (commit_ptr_next),
      .dout(commit_ptr),
      .*
  );
  reg_lr #(1) commit_ptr_flag_r (
      .rst (rst | o_redirect_valid),
      .load(o_commit_fire),
      .din (commit_ptr_flag_next),
      .dout(commit_ptr_flag),
      .*
  );
  //Cacualte entry_num and generate i_write_ready, o_commit_valid signal
  wire ptr_next_equal = (i_write_fire ? write_ptr_next : write_ptr) == (o_commit_fire ? commit_ptr_next : commit_ptr);
  wire flag_next_equal = (i_write_fire ? write_ptr_flag_next : write_ptr_flag) == (o_commit_fire ? commit_ptr_flag_next : commit_ptr_flag);
  ROB_PTR ptr_next_delta;
  assign ptr_next_delta = (i_write_fire ? write_ptr_next : write_ptr) - (o_commit_fire ? commit_ptr_next : commit_ptr);
  wire [`ROB_AW-1:0] rob_entry_num_next = flag_next_equal ? {ptr_next_delta} : {ptr_next_delta} + `ROB_SIZE ;
  wire i_write_ready_next = rob_entry_num_next[`ROB_AW-1:0] <= (`ROB_SIZE - WRITE_WIDTH);
  reg_r #(1) i_write_ready_r (
      .rst (rst | o_redirect_valid),
      .din (i_write_ready_next),
      .dout(i_write_ready),
      .*
  );

  wire empty_next = ptr_next_equal & flag_next_equal;
  reg_r #(1) o_commit_valid_r (
      .rst (rst | o_redirect_valid),
      .din (~empty_next),
      .dout(o_commit_valid),
      .*
  );

  genvar gi;
  generate
    for (gi = 0; gi < WRITE_WIDTH; gi = gi + 1) begin : WptrsGen
      ROB_PTR base_ptr, write_idx_next;
      assign base_ptr = write_ptr_next;
      wire [`ROB_AW:0] write_addr = {1'b0, base_ptr} + gi;
      wire [`ROB_AW-1:0] write_idx_next_round = write_addr[`ROB_AW-1:0] - `ROB_SIZE;
      wire write_idx_flag = write_addr >= `ROB_SIZE;
      assign write_idx_next = write_idx_flag ? write_idx_next_round[`ROB_AW-1:0] : write_addr[`ROB_AW-1:0];
      reg_lr #(`ROB_AW, gi) i_write_rob_idx_r (
          .rst (rst | o_redirect_valid),
          .load(i_write_fire),
          .din (write_idx_next),
          .dout(i_write_rob_idx[gi]),
          .*
      );
    end

    for (gi = 0; gi < `ROB_SIZE; gi = gi + 1) begin : ROB
      logic write_valid, we;
      ROB_Entry wdata;
      logic [WRITE_WIDTH-1:0] sel;
      always_comb begin
        integer i;
        for (i = 0; i < WRITE_WIDTH; i = i + 1) begin
          sel[i] = i_write_op_valid[i] & (i_write_rob_idx[i] == gi);
        end
      end
      MUX_OH #(WRITE_WIDTH, 1) mux_we (
          .sel,
          .din (i_write_op_valid),
          .dout(write_valid)
      );
      assign we = write_valid & i_write_fire;
      MUX_OH #(WRITE_WIDTH, ROB_ENTRY_SIZE) mux_wdata (
          .sel,
          .din (i_write_op),
          .dout(wdata)
      );
      reg_l #(ROB_ENTRY_SIZE) op_entry_r (
          .load(we),
          .din (wdata),
          .dout(rob_entrys[gi]),
          .*
      );
      // op_finish
      logic [ISSUE_WIDTH-1:0] finish_sel;
      logic finish_set;
      always_comb begin
        integer i;
        for (i = 0; i < ISSUE_WIDTH; i = i + 1) begin
          finish_sel[i] = i_finish_valid[i] & (i_finish_rob_idx[i] == gi);
        end
      end
      MUX_OH #(ISSUE_WIDTH, 1) mux_finish (
          .sel (finish_sel),
          .din (i_finish_valid),
          .dout(finish_set)
      );
      reg_lr #(1) op_finish_r (
          .load(we | finish_set),
          .din (finish_set),
          .dout(op_finish[gi]),
          .*
      );

      // op_issue
      logic [ISSUE_WIDTH-1:0] issue_sel;
      logic issue_set;
      always_comb begin
        integer i;
        for (i = 0; i < ISSUE_WIDTH; i = i + 1) begin
          issue_sel[i] = i_issue_valid[i] & (i_issue_rob_idx[i] == gi);
        end
      end
      MUX_OH #(ISSUE_WIDTH, 1) mux_issue (
          .sel (issue_sel),
          .din (i_issue_valid),
          .dout(issue_set)
      );
      reg_lr #(1) op_issue_r (
          .load(we | issue_set),
          .din (issue_set),
          .dout(op_issue[gi]),
          .*
      );

      // op_replay
      logic [ISSUE_WIDTH-1:0] replay_sel;
      logic replay_set;
      always_comb begin
        integer i;
        for (i = 0; i < ISSUE_WIDTH; i = i + 1) begin
          replay_sel[i] = i_replay_valid[i] & (i_replay_rob_idx[i] == gi);
        end
      end
      MUX_OH #(ISSUE_WIDTH, 1) mux_replay (
          .sel (replay_sel),
          .din (i_replay_valid),
          .dout(replay_set)
      );
      reg_lr #(1) op_replay_r (
          .load(we | replay_set),
          .din (replay_set),
          .dout(op_replay[gi]),
          .*
      );

      wire misc_sel = i_misc_redirect_valid & (i_misc_redirect_rob_idx == gi);
      // op bpred miss
      logic [BRU_NUM-1:0] bru_sel;
      logic bru_set;
      always_comb begin
        integer i;
        for (i = 0; i < BRU_NUM; i = i + 1) begin
          bru_sel[i] = i_bru_bpred_miss_valid[i] & (i_bru_rob_idx[i] == gi);
        end
      end
      MUX_OH #(BRU_NUM, 1) mux_bru (
          .sel (bru_sel),
          .din (i_bru_bpred_miss_valid),
          .dout(bru_set)
      );
      reg_lr #(1) op_bpred_miss_r (
          .load(we | bru_set),
          .din (bru_set & (~misc_sel)),
          .dout(op_bpred_miss[gi]),
          .*
      );
      // op_correct_bpu_res
      BPU_Res correct_bpu_res_write;
      MUX_OH #(BRU_NUM, BPU_RES_SIZE) mux_correct_bpu_res_r (
          .sel (bru_sel),
          .din (i_bru_correct_bpu_res),
          .dout(correct_bpu_res_write)
      );
      reg_l #(BPU_RES_SIZE) op_correct_bpu_res_r (
          .load(we | bru_set),
          .din (correct_bpu_res_write),
          .dout(op_correct_bpu_res[gi]),
          .*
      );
      //op_dnpc
      word_t dnpc_write;
      MUX_OH #(BRU_NUM, `WORD_BITS) mux_dnpc_r (
          .sel (bru_sel),
          .din (i_bru_dnpc),
          .dout(dnpc_write)
      );
      reg_l #(`WORD_BITS) op_dnpc_r (
          .load(we | bru_set | misc_sel),
          .din (misc_sel ? i_misc_redirect_pc : dnpc_write),
          .dout(op_dnpc[gi]),
          .*
      );

      // op_exeception
      logic [ISSUE_WIDTH-1:0] exeception_sel;
      logic exeception_set;
      always_comb begin
        integer i;
        for (i = 0; i < ISSUE_WIDTH; i = i + 1) begin
          exeception_sel[i] = i_exeception_valid[i] & (i_exeception_rob_idx[i] == gi);
        end
      end
      MUX_OH #(ISSUE_WIDTH, 1) mux_exeception (
          .sel (exeception_sel),
          .din (i_exeception_valid),
          .dout(exeception_set)
      );
      reg_lr #(1) op_exeception_r (
          .load(we | exeception_set),
          .din (exeception_set),
          .dout(op_exeception[gi]),
          .*
      );

      // op_exeception_code
      EXPT_CODE exeception_code_write;
      MUX_OH #(ISSUE_WIDTH, EXPT_CODE_SIZE) mux_exeception_code_r (
          .sel (exeception_sel),
          .din (i_exeception_code),
          .dout(exeception_code_write)
      );
      reg_l #(EXPT_CODE_SIZE) op_exeception_code_r (
          .load(we | exeception_set),
          .din (exeception_code_write),
          .dout(op_exeception_code[gi]),
          .*
      );

      // op_mmio
      logic [LSU_NUM-1:0] mmio_sel;
      logic mmio_set;
      always_comb begin
        integer i;
        for (i = 0; i < LSU_NUM; i = i + 1) begin
          mmio_sel[i] = i_mmio_valid[i] & (i_mmio_rob_idx[i] == gi);
        end
      end
      MUX_OH #(LSU_NUM, 1) mux_mmio (
          .sel (mmio_sel),
          .din (i_mmio_valid),
          .dout(mmio_set)
      );
      reg_lr #(1) op_mmio_r (
          .load(we | mmio_set),
          .din (mmio_set),
          .dout(op_mmio[gi]),
          .*
      );
    end
    logic [COMMIT_WIDTH-1:0] entry_valid;
    logic [COMMIT_WIDTH-1:0] entry_finish;
    logic [COMMIT_WIDTH-1:0] redirect_valid;
    logic [COMMIT_WIDTH-1:0] redirect_replay;

    logic redirect_bpu_res_valid[COMMIT_WIDTH-1:0];
    BPU_Res redirect_bpu_res[COMMIT_WIDTH-1:0];
    word_t redirect_pc[COMMIT_WIDTH-1:0];
    ROB_Entry commit_entry[COMMIT_WIDTH-1:0];
    ROB_State commit_state[COMMIT_WIDTH-1:0];
    ROB_PTR commit_rob_idx[COMMIT_WIDTH-1:0];

    for (gi = 0; gi < COMMIT_WIDTH; gi = gi + 1) begin : CommitGen
      ROB_PTR base_ptr, commit_idx_next;
      assign base_ptr = commit_ptr_next;
      wire [`ROB_AW:0] commit_addr = {1'b0, base_ptr} + gi;
      wire [`ROB_AW-1:0] commit_idx_next_round = commit_addr[`ROB_AW-1:0] - `ROB_SIZE;
      wire commit_idx_flag_next = commit_addr >= `ROB_SIZE;
      assign commit_idx_next = commit_idx_flag_next ? commit_idx_next_round[`ROB_AW-1:0] : commit_addr[`ROB_AW-1:0];
      reg_lr #(`ROB_AW, gi) commit_rob_idx_r (
          .rst (rst | o_redirect_valid),
          .load(o_commit_fire),
          .din (commit_idx_next),
          .dout(commit_rob_idx[gi]),
          .*
      );
      reg_r #(1) entry_valid_r (
          .rst (rst | o_redirect_valid),
          .din (gi < rob_entry_num_next[`ROB_AW-1:0]),
          .dout(entry_valid[gi]),
          .*
      );
      assign entry_finish[gi] = entry_valid[gi] & commit_state[gi].finish;
      assign redirect_valid[gi] = (&entry_finish[gi:0]) & (commit_state[gi].replay | commit_state[gi].exeception | commit_state[gi].bpred_miss);
      assign redirect_replay[gi] = (&entry_finish[gi:0]) & commit_state[gi].replay;
      assign redirect_bpu_res_valid[gi] = (&entry_finish[gi:0]) & commit_state[gi].bpred_miss;
      assign redirect_bpu_res[gi] = op_correct_bpu_res[commit_rob_idx[gi]];
      assign redirect_pc[gi] = commit_entry[gi].rop.fop.pc;
      assign o_commit_op[gi] = commit_entry[gi];
      assign o_commit_op_state[gi] = commit_state[gi];
      assign o_commit_restore_gc[gi] = redirect_valid[gi];
      assign commit_entry[gi] = rob_entrys[commit_rob_idx[gi]];
      assign commit_state[gi] = rob_states[commit_rob_idx[gi]];
    end
    assign o_order_valid = entry_valid[0];
    assign o_order_rob_idx = commit_rob_idx[0];
    assign o_commit_op_valid[0] = entry_finish[0];
    for (gi = 1; gi < COMMIT_WIDTH; gi = gi + 1) begin : CommitOPGen
      assign o_commit_op_valid[gi] = (&entry_finish[gi:0]) & (~|redirect_valid[gi-1:0]);
    end
    //Generate Redirect Signal

    assign o_redirect_valid = o_commit_fire & (|redirect_valid);
    assign o_redirect_replay = o_commit_fire &(|redirect_replay);
    MUX_PRIO #(COMMIT_WIDTH, 1) mux_o_redirect_bpu_res_valid (
        .sel (redirect_valid),
        .din (redirect_bpu_res_valid),
        .dout(o_redirect_bpu_res_valid)
    );
    MUX_PRIO #(COMMIT_WIDTH, BPU_RES_SIZE) mux_o_redirect_bpu_res (
        .sel (redirect_valid),
        .din (redirect_bpu_res),
        .dout(o_redirect_bpu_res)
    );
    MUX_PRIO #(COMMIT_WIDTH, `WORD_BITS) mux_o_redirect_pc (
        .sel (redirect_valid),
        .din (redirect_pc),
        .dout(o_redirect_pc)
    );

  `PERF_EVENT(rob_redirect, o_redirect_valid);
  `PERF_EVENT(rob_commit_stall_by_lsu, entry_valid[0] & ~entry_finish[0] & commit_entry[0].rop.fop.dop.fu_type[FU_LSU_BIT]);
    `ifdef ROB_DIFFTEST
    ROB_PTR redirect_ptr;
    MUX_PRIO #(COMMIT_WIDTH, `ROB_AW) mux_redirect_ptr (
        .sel (redirect_valid),
        .din (commit_rob_idx),
        .dout(redirect_ptr)
    );
`endif
  endgenerate

`ifdef ROB_DIFFTEST
  localparam ROB_STATE_SIZE = $size(ROB_State);
  ROB_Entry o_redirect_op  /* verilator public */;
  MUX_PRIO #(COMMIT_WIDTH, ROB_ENTRY_SIZE) mux_o_redirect_op (
      .sel (redirect_valid),
      .din (commit_entry),
      .dout(o_redirect_op)
  );
  ROB_State o_redirect_op_state  /* verilator public */;
  MUX_PRIO #(COMMIT_WIDTH, ROB_STATE_SIZE) mux_o_redirect_op_state (
      .sel (redirect_valid),
      .din (commit_state),
      .dout(o_redirect_op_state)
  );

  int id = 0;
  `define ToInt(val) {{($size(int)-$size(val)){1'b0}},val}
  always_ff @(posedge clk) begin
    integer i;
    if (rst) begin
      id <= rob_create(id, `ROB_SIZE, $sformatf("%m"));
    end else begin
      if (o_commit_fire) begin
        for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
          if (o_commit_op_valid[i])
            rob_pop(
            id,
            i,
            `ToInt(commit_rob_idx[i]),
            commit_state[i].issue,
            commit_state[i].finish,
                    commit_state[i].exeception,
                    `ToInt(commit_state[i].exeception_code));
        end
      end
      for (i = 0; i < ISSUE_WIDTH; i = i + 1) begin
        if (i_issue_valid[i])
          rob_modify(
          id,
          i,
          `ToInt(i_issue_rob_idx[i]),
          'b1,
          'b0,
          'b0,
          'b0);
        if (i_finish_valid[i])
          rob_modify(
          id,
          i,
          `ToInt(i_finish_rob_idx[i]),
          'b0,
          'b1,
          'b0,
          'b0);
        if (i_exeception_valid[i])
          rob_modify(
          id,
          i,
          `ToInt(i_exeception_rob_idx[i]),
          'b0,
          'b0,
          'b1,
                     `ToInt(i_exeception_code[i]));
      end
      if (i_write_fire) begin
        for (i = 0; i < WRITE_WIDTH; i = i + 1) begin
          if (i_write_op_valid[i])
            rob_push(
            id,
            i,
            `ToInt(i_write_rob_idx[i]));
        end
      end
      if (o_redirect_valid)
        rob_redirect(
        id,
        `ToInt(redirect_ptr),
        o_redirect_op_state.issue,
                     o_redirect_op_state.finish, o_redirect_op_state.exeception,
                     `ToInt(o_redirect_op_state.exeception_code));
    end
  end
`endif
endmodule

